#ifdef __GNUG__
#pragma implementation
#endif

#include "Class.h"
#include "Zoomer.h"
#include "Panner.h"
#include "BorderItems.h"
#include "Box.h"
#include "Buttons.h"
#include "Filler.h"
#include "WindowSystem.h"
#include "System.h"
#include "ObjectTable.h"
#include "AccessMem.h"
#include "ByteArray.h"
#include "OrdColl.h"
#include "Alert_e.h"
#include "String.h"
#include "Menu.h"
#include "MenuBar.h"
#include "Error.h"

#include "OStruBrowser.h"
#include "EtPeCmdNo.h"
#include "ClassItem.h"

static OrdCollection *graphViews= 0;

//---- PeFindSelReferences ------------------------------------------------

class PeFindSelReferences: public AccessObjPtrs {
	Object *referencesTo;
	Collection *col;
	Collection *names;
public:
	PeFindSelReferences(Object *ref, Collection *cp, Collection *l= 0)
		{ col= cp; names= l; referencesTo= ref;}
	void FoundPointer(Object *op, char *n, int i, bool);
};

void PeFindSelReferences::FoundPointer(Object *op, char *n, int i, bool)
{
	if (op && op == referencesTo) {
		char *cp;
		col->Add(inObject);
		if (i != -1)
			cp= form("%s[%d]", n, i);
		else
			cp= n;
		if (names)
			names->Add(new ByteArray(n));
	}
}

//---- PeFindSelPtrs -----------------------------------------------------------

class PeFindSelPtrs: public AccessObjPtrs {
	Collection *col;
	Collection *names;
public:
	PeFindSelPtrs(Collection *cp, Collection *n= 0)
		{ col= cp; names= n; }
	void FoundPointer(Object *op, char*, int, bool);
};

void PeFindSelPtrs::FoundPointer(Object *op, char *name, int, bool)
{
	if (op)
		col->Add(op);
	if (names)
		names->Add(new ByteArray(name));
}

//---- PeObjectStructureView ------------------------------------------------------------

NewMetaImpl(PeObjectStructureView, GraphView, (TB(lines)));

static Ink *depPat, *evtPat, *refPat, *contPat, *pointPat;
static int width;

PeObjectStructureView::PeObjectStructureView(EvtHandler *evt) : GraphView(evt)
{
	if (depPat == 0) { // init colors
		if (gColor) {
			depPat= new_RGBColor(255, 0, 255);
			evtPat= new_RGBColor(255, 0, 0);
			contPat= new_RGBColor(0, 255, 128);
			refPat= new_RGBColor(0, 0, 255);
			pointPat= new_RGBColor(0, 255, 255);
			width= 3;
		} else {
			depPat= ePatGrey50;
			evtPat= ePatGrey75;
			contPat= ePatGrey25;
			refPat= ePatGrey60;
			pointPat= ePatBlack;
			width= 6;
		}
	}
	currentOp= 0;
	lines= varNames= TRUE;

	if (graphViews == 0)
		graphViews= new OrdCollection;
	graphViews->Add(this);
}

PeObjectStructureView::~PeObjectStructureView()
{
	graphViews->RemovePtr(this);
	if (graphViews->Size() == 0)
		SafeDelete(graphViews);
}

Object *PeObjectStructureView::GetRoot()
{
	return rootOp;
}

VObject *PeObjectStructureView::NodeAsVObject(Object *vop)
{
	char buf[20], *cp;
	buf[0]= '\0';
	vop->InspectorId(buf, sizeof buf);
	if (strlen(buf))
		cp= buf;
	else
		cp= form("0x%x", (int) vop);
	return
		new BackgroundItem(ePatWhite,
			new Cluster(cIdNone, eVObjHCenter, 0,
				new TextItem(vop->ClassName(), gFixedFont->WithFace(eFaceBold),
					Point(2,1)
				),
				new PeObjectItem(cIdObjItem, cp, vop),
				0
			)
		);
}

Iterator *PeObjectStructureView::MakeSubPartsIter(Object *op)
{
	OrdCollection *col= new OrdCollection;
	op->CollectParts(col);
	return new PeNestedIter(col, TRUE);
}

Command *PeObjectStructureView::NodeSelected(VObject *t, int cl)
{
	Command *cmd= GraphView::NodeSelected(t, cl);

	VObject *vop= t->FindItem(cIdObjItem);
	if (vop) {
		Object *op= Guard(vop, PeObjectItem)->GetObject();
		if (ObjectTable::PtrIsValid(op)) {
			if (cl > 1)
				op->Inspect();
			else
				currentOp= op;
		} else
			ShowAlert(eAlertNote, "instance 0x%x disappeard", op);
	}
	return cmd;
}

void PeObjectStructureView::LoadETGraph(Object *root)
{
	RemoveAllPaths();
	RemoveAllReferences();
	EmptyGraph();
	rootOp= root;
	BuildGraphBFS(rootOp);
	GraphNode *gn= AssociatedGraphNode(rootOp);
	SetGraph(gn);
	Rectangle r= gn->Image()->ContentRect();
	RevealRect(r.Expand(Point(0,200)), r.extent);
}

bool PeObjectStructureView::CheckObject(Object *op)
{
	if (!ObjectTable::PtrIsValid(op)) {
		ShowAlert(eAlertNote, "instance 0x%x disappeard", op);
		return FALSE;
	}
	return TRUE;
}

void PeObjectStructureView::SetSelection(VObject *vop)
{
	GraphView::SetSelection(vop);
	SynchSelection();
}

void PeObjectStructureView::SynchSelection()
{
	VObject *sel= GetSelection();
	PeObjectItem *op= 0, *op2= 0;

	if (sel)
		op= Guard(sel->FindItem(cIdObjItem), PeObjectItem);
	if (graphViews && op) {
		Iter next(graphViews);
		PeObjectStructureView *tosvp;
		while (tosvp= (PeObjectStructureView*)next()) {
			VObject *sel2= tosvp->GetSelection();
			if (sel2)
				op2= Guard(sel2->FindItem(cIdObjItem), PeObjectItem);
			if (op2->GetObject() != op->GetObject())
				tosvp->ShowObject(op->GetObject());
		}
	}
}

void PeObjectStructureView::EvtHandlers(Object *op)
{
	if (!CheckObject(op))
		return;
	if (!op->IsKindOf(EvtHandler))
		return;
	EvtHandler *ep= (EvtHandler*)op;
	OrdCollection *oc= new OrdCollection;
	oc->Add(ep);
	while (ep= ep->GetNextHandler())
		oc->Add(ep);
	if (oc->Size()) {
		GraphPath *gp= new GraphPath(this, oc, evtPat, width, TRUE);
		AddPath(gp);
	} else {
		ShowAlert(eAlertNote, "No next eventhandler");
		SafeDelete(oc);
	}
}

void PeObjectStructureView::Containers(Object *op)
{
	if (!CheckObject(op))
		return;
	if (!op->IsKindOf(VObject))
		return;
	VObject *vp= (VObject*)op;
	OrdCollection *oc= new OrdCollection;
	oc->Add(vp);
	while (vp= vp->GetContainer())
		oc->Add(vp);
	if (oc->Size()) {
		GraphPath *gp= new GraphPath(this, oc, contPat, width, TRUE);
		AddPath(gp);
	} else {
		ShowAlert(eAlertNote, "No container");
		SafeDelete(oc);
	}
}

void PeObjectStructureView::Dependents(Object *op)
{
	if (!CheckObject(op))
		return;
	Iter next(op->GetObserverIter());
	OrdCollection *oc= new OrdCollection;
	Object *dep;
	while (dep= next())
		oc->Add(dep);
	GraphReference *gp= new GraphReference(this, op, oc, depPat, width, TRUE);
	AddReference(gp);
}

void PeObjectStructureView::References(Object *op)
{
	if (!CheckObject(op))
		return;
	GrShowWaitCursor cw;
	OrdCollection *refs= new OrdCollection, *names= 0;
	if (varNames)
		names= new OrdCollection;
	PeFindSelReferences collector(op, refs, names);
	Object *cop;
	{
		ObjectTableIter next(0);

		while (cop= next()) {
			collector.ForObject(cop);
			cop->IsA()->EnumerateMembers(&collector);
		}
	}
	if (refs->Size()) {
		GraphReference *gp;
		if (varNames)
			gp= new PeLabeledGraphReference(this, op, refs, names, refPat, 2, TRUE);
		else
			gp= new GraphReference(this, op, refs, refPat, 2, TRUE);
		AddReference(gp);
	} else {
		ShowAlert(eAlertNote, "No references");
		SafeDelete(refs);
		SafeDelete(names);
	}
}

void PeObjectStructureView::Pointers(Object *op)
{
	if (!CheckObject(op))
		return;
	GrShowWaitCursor cw;
	OrdCollection *pointers= new OrdCollection, *names= 0;
	if (varNames)
		names= new OrdCollection;
	if (op->IsA()) {
		PeFindSelPtrs collector(pointers, names);
		collector.ForObject(op);
		op->IsA()->EnumerateMembers(&collector);
	}
	if (pointers->Size()) {
		GraphReference *gp;
		if (varNames)
			gp= new PeLabeledGraphReference(this, op, pointers, names, pointPat, 2, TRUE);
		else
			gp= new GraphReference(this, op, pointers, pointPat, 2, TRUE);
		AddReference(gp);
	} else {
		ShowAlert(eAlertNote, "No pointers");
		SafeDelete(pointers);
		SafeDelete(names);
	}
}

void PeObjectStructureView::ShowObject(Object *op)
{
	VObject *vop= AssociatedVObject(op);
	if (vop) {
		currentOp= op;
		SetSelection(vop);
		RevealRect(vop->ContentRect().Expand(100), Point(0,100));
	}
}

void PeObjectStructureView::ToggleShowLines()
{
	lines= !lines;
	if (lines)
		SetConnType(eTCDiagonal);
	else
		SetConnType(eTCNone);
}

Command *PeObjectStructureView::DoMenuCommand(int cmd)
{
	switch (cmd) {
	case cEVTHANDLER:
		EvtHandlers(currentOp);
		break;
		
	case cCONTAINER:
		Containers(currentOp);
		break;

	case cDEPENDENCIES:
		Dependents(currentOp);
		break;

	case cREFERENCES:
		References(currentOp);
		break;

	case cPOINTERS:
		Pointers(currentOp);
		break;

	case cPROMOTE:
		Control(cPeOStruBrowser, cPeSpawnOBrowser, currentOp);
		break;

	case cDOINSPECT:
		if (!ObjectTable::PtrIsValid(currentOp))
			ShowAlert(eAlertNote, "instance 0x%x disappeard", currentOp);
		else
			currentOp->Inspect();
		break;

	case cSHOWLABELS:
		varNames= !varNames;
		break;
		
	case cUPDATE:
		LoadETGraph(GetRoot());
		break;
		
	case cCLEARPATH:
		RemoveAllPaths();
		RemoveAllReferences();
		break;
		
	case cSHOWLINES:
		ToggleShowLines();
		break;

	case cSPAWN:
		GraphView::Control(cPeOStruBrowser, cPeSpawnOBrowser, GetRoot());
		break;
		
	default:
		break;
	}
	return GraphView::DoMenuCommand(cmd);
}

void PeObjectStructureView::DoSetupMenu(Menu *menu)
{
	GraphView::DoSetupMenu(menu);
	menu->CheckItem(cSHOWLINES, lines, cItemStateManyOf);
	menu->CheckItem(cSHOWLABELS, varNames, cItemStateManyOf);

	menu->EnableItems(cUPDATE, cCLEARPATH, cSHOWLINES, cSHOWLABELS, 0);
	if (currentOp) {
		Iterator *it= currentOp->GetObserverIter();
		menu->EnableItems(cREFERENCES, cDOINSPECT, cPOINTERS, cPROMOTE, 0);
		if (currentOp->IsKindOf(EvtHandler))
			menu->EnableItem(cEVTHANDLER);
		if (it) {
			SafeDelete(it);
			menu->EnableItem(cDEPENDENCIES);
		}
		if (currentOp->IsKindOf(VObject))
			menu->EnableItem(cCONTAINER);
	}
}

//---- PeLabeledGraphReference ------------------------------------------------------------

PeLabeledGraphReference::PeLabeledGraphReference(
			GraphView *gv, Object *op, Collection *n, SeqCollection *l,
					Ink *i, int w, bool f) : GraphReference(gv, op, n, i, w, f)
{
	labels= l;
}

PeLabeledGraphReference::~PeLabeledGraphReference()
{
	SafeDelete(labels);
}

void PeLabeledGraphReference::DrawConnection(int n, VObject *from, VObject *to)
{
	GraphReference::DrawConnection(n, from, to);

	Point p1= from->contentRect.Center();
	Point p2= to->contentRect.Center();
	Rectangle r1= NormRect(p1, p2);
	ByteArray *b= (ByteArray*)labels->At(n);
	Font *fp= gFixedFont->WithFace(eFaceItalic);
	char *name= (char*) b->Str();
	Rectangle r2= fp->BBox((byte*)name);
	Point p= r1.Center() - r2.Center();
	GrEraseRect(Rectangle(p, r2.extent));
	GrShowString(fp, ePatBlack, p + Point(0, fp->Ascender()), (byte*)name);
}

//---- PeObjectBrowser -----------------------------------------------------------

NewMetaImpl(PeObjectBrowser, EtPeTool, (TP(osv)));

PeObjectBrowser *PeObjectBrowser::objBrowser;

PeObjectBrowser::PeObjectBrowser(Manager *m) : EtPeTool(m, "Object Structure Browser")
{
}

VObject *PeObjectBrowser::DoMakeContent()
{
	osv= new PeObjectStructureView(this);
	zoomer= new Zoomer(osv);
	SetFirstHandler(osv);
	return zoomer;
}

Point PeObjectBrowser::GetInitialWindowSize()
{
	return Point(600,600);
}

PeObjectBrowser::~PeObjectBrowser()
{
	SafeDelete(osv);
}

MenuBar *PeObjectBrowser::DoMakeMenuBar()
{
	MenuBar *mb= EtPeTool::DoMakeMenuBar();

	Menu *m= Zoomer::MakeMenu(cVIEWMENU);
	m->InsertItemsBefore(cZOOMOUT,
					"Show Names",         cSHOWLABELS,
					"Show Lines",         cSHOWLINES,
					"-",
					"Update",             cUPDATE,
					"-",
					0);
	mb->AddMenu(m);

	m= new Menu("Objects");
	m->AppendItems(
					"Event Handlers",     cEVTHANDLER,
					"Containers",         cCONTAINER,
					"Dependencies",       cDEPENDENCIES,
					"-",
					"Referenced From",    cREFERENCES,
					"Points To",          cPOINTERS,
					"-",
					"Clear",              cCLEARPATH,
					"-",
					"Promote",            cPROMOTE,
					"Inspect",            cDOINSPECT,
					0);
	mb->AddMenu(m);

	return mb;
}

void PeObjectBrowser::DoSetupMenu(Menu *m)
{
	EtPeTool::DoSetupMenu(m);
	Clipper *vf= zoomer->GetClipper();
	if (vf->GetZoom() < 1024)
		m->EnableItem(cZOOMIN);
	if (vf->GetZoom() > 0.01)
		m->EnableItem(cZOOMOUT);
	m->EnableItem(cZOOMRESET);
}

void PeObjectBrowser::ShowObject(Object *op)
{
	osv->LoadETGraph(op);
}

Command *PeObjectBrowser::DoMenuCommand(int cmd)
{
	Clipper *vf= zoomer->GetClipper();
	switch (cmd) {
	case cZOOMOUT:
		vf->SetZoom(vf->GetZoom()/1.2);
		break;
		
	case cZOOMIN:
		vf->SetZoom(vf->GetZoom()*1.2);
		break;
		
	case cZOOMRESET:
		vf->SetZoom(1.0);
		break;
				
	default:
		return EtPeTool::DoMenuCommand(cmd);
	}
	return gNoChanges;
}

void PeObjectBrowser::Spawn(Manager *m, Object *op)
{
	PeObjectBrowser *ob= new PeObjectBrowser(m);
	ob->SetOnDismiss(eMgrClose);
	ob->Show();
	ob->ShowObject(op);
}

void PeObjectBrowser::ShowObject(Manager *m, Object *op)
{
	if (objBrowser == 0)
		objBrowser= new PeObjectBrowser(m);
	objBrowser->Show();
	objBrowser->ShowObject(op);
}

//---- PeNestedIter ----------------------------------------------------------

PeNestedIter::PeNestedIter(Object *op1, Object *va_(op2), ...)
{
	va_list ap;
	va_start(ap,va_(op1));
	cont= new OrdCollection(20);
	Object *op;

	cont->Add(op1);
	cont->Add(va_(op2));
	while (op= va_arg(ap, Object*))
		cont->Add(op);
	it= cont->MakeIterator();
	subIter= 0;
	free= TRUE;
}

PeNestedIter::PeNestedIter(Collection *col, bool f)
{
	cont= col;
	it= cont->MakeIterator();
	subIter= 0;
	free= f;
}

PeNestedIter::~PeNestedIter()
{
	if (free)
		SafeDelete(cont);
	SafeDelete(it);
	SafeDelete(subIter);
}

void PeNestedIter::Reset()
{
	Error("PeNestedIter::Reset", "May not call Reset");
}

Collection *PeNestedIter::Coll()
{
	Error("PeNestedIter::Coll", "May not call Coll");
	return 0;
}

Object *PeNestedIter::operator()()
{
	Object *op= 0;

	while (op == 0) {
		if (subIter) {
			op= subIter->operator()();
			if (op == 0) {
				SafeDelete(subIter);
				continue;
			}
		}
		else {
			op= it->operator()();
			if (op && op->IsKindOf(Collection)) {
				subIter= ((Collection*)op)->MakeIterator();
				op= 0;
				continue;
			}
			else
				break;
		}
	}
	return op;
}
